package com.sailing.xjpb.orm.intercepter;

import com.sailing.xjpb.common.sercurity.SailingUserContext;
import com.sailing.xjpb.orm.utils.SailingTenantSqlParser;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


/**
 * 租户持久层拦截器：拦截简单的增删改查sql，增加必要的租户查询和插入参数
 *
 * @author FanHaiChao
 * createDate 2024/1/11 12:16
 * @see com.sailing.xjpb.common.operator.SailingTenant
 * @see com.sailing.xjpb.orm.repository.SailingTenantRepository
 */
@Slf4j
public class TenantStatementInterceptor implements StatementInspector {

    /**
     * 用户上下文
     */
    private static SailingUserContext sailingUserContext;

    /**
     * 错误信息
     */
    private static final String ERROR_MSG = "租户权限sql解析出错，sql:{}";

    /**
     * 系统表信息，不用拦截
     */
    private static final List<String> SYSTEM_TABLE_LIST = Arrays.asList("information_schema", "pg_tables", "pg_class");

    /**
     * 数据库连接信息配置
     */
    private static SailingTenantTableInitialization tableInitialization;

    /**
     * 设置据库配置信息
     *
     * @param tableInitialization 数据库配置信息处理器
     */
    public static void setSailingDataBaseInfoHandler(SailingTenantTableInitialization tableInitialization) {
        if (tableInitialization != null) {
            TenantStatementInterceptor.tableInitialization = tableInitialization;
        }
    }

    /**
     * 设置用户上下文信息
     *
     * @param sailingUserContext 用户上下文
     */
    public static void setUserContext(SailingUserContext sailingUserContext) {
        if (sailingUserContext != null) {
            TenantStatementInterceptor.sailingUserContext = sailingUserContext;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String inspect(String sql) {
        try {
            //是否有多租户的标识
            boolean isTenantFlag = SailingCrudRepositoryContext.getSqlTenantCodeFlag();
            if (isTenantFlag && isContainsTenant(sql)) {
                String tenantCode = sailingUserContext.getTenantCode();
                //获取租户权限的sql
                return SailingTenantSqlParser.getTenantSql(sql, tenantCode);
            }
            //如果没有多租户标识，则直接返回sql，不做多租户处理
            return sql;
        } catch (Exception e) {
            log.error(ERROR_MSG, sql, e);
            return sql;
        } finally {
            SailingCrudRepositoryContext.clean();
        }
    }

    /**
     * 表中是否包含租户编码
     *
     * @param sql sql
     * @return 是否包含租户编码, true:是，false:否
     * @throws JSQLParserException sql解析异常
     */
    private boolean isContainsTenant(String sql) throws JSQLParserException {
        //如果是系统表信息，则不用拦截，否则查不出表字段等
        for (String sysTable : SYSTEM_TABLE_LIST) {
            if (sql.contains(sysTable)) {
                return false;
            }
        }
        //sql中的表名
        Set<String> tableNames = getTableNames(sql);
        return tableNames.stream().anyMatch(tableInitialization::isTenantTable);
    }

    /**
     * 获取sql中的表名
     *
     * @param sql 转换的sql
     * @return 转换后的sql
     * @throws JSQLParserException sql解析异常
     */
    private Set<String> getTableNames(String sql) throws JSQLParserException {
        Statement statement = CCJSqlParserUtil.parse(sql);
        TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
        List<String> tableNameList = tablesNamesFinder.getTableList(statement);
        //获取当前数据库连接schema
        String currentSchema = tableInitialization.getCurrentSchema();
        return tableNameList.stream().map(tableName -> tableName.replace(currentSchema + ".", "")).collect(Collectors.toSet());
    }
}
